package config

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"time"

	"github.com/tsaikd/KDGoLib/errutil"
	"golang.org/x/sync/errgroup"
	yaml "gopkg.in/yaml.v2"

	"github.com/tsaikd/gogstash/config/ctxutil"
	"github.com/tsaikd/gogstash/config/goglog"
	"github.com/tsaikd/gogstash/config/logevent"
)

// 定义了一系列错误常量，用于在配置文件处理过程中捕捉特定的错误情况。这些错误通常来源于文件读取、JSON/YAML格式解析和配置状态验证等方面。

var (
	// ErrorReadConfigFile1：当尝试读取配置文件时发生错误时使用此错误代码。
	// %q 表示将提供一个字符串参数，作为出错的具体文件路径。
	ErrorReadConfigFile1 = errutil.NewFactory("Failed to read config file: %q")

	// ErrorUnmarshalJSONConfig：在尝试解析配置文件内容为 JSON 格式时发生错误使用此代码。
	// %v 表示将提供一个时间参数，表示超时时间。
	ErrorUnmarshalJSONConfig = errutil.NewFactory("Failed unmarshalling config in JSON format")

	// ErrorUnmarshalYAMLConfig：在尝试解析配置文件内容为 YAML 格式时发生错误使用此代码。
	ErrorUnmarshalYAMLConfig = errutil.NewFactory("Failed unmarshalling config in YAML format")

	// ErrorTimeout1：当处理配置相关的操作超时时，使用此错误代码。
	// %v 表示将提供一个时间参数，表示具体的超时时间值。
	ErrorTimeout1 = errutil.NewFactory("timeout: %v")

	// ErrorInvalidState：在尝试暂停或恢复某些状态时，如果当前状态无效，则使用此错误代码。
	ErrorInvalidState = errutil.NewFactory("Invalid state for pause/resume")

	// ErrorNoFilterName：当配置中没有提供有效的过滤器名称时，使用此错误代码。这可能是由于配置语法错误导致的。
	// %s 表示将提供一个字符串参数，表示具体配置部分的名称。
	ErrorNoFilterName = errutil.NewFactory("No name - probably invalid syntax in configuration section %s")
)

type Config struct {
	// JSON 和 YAML 解析时的输入配置对象数组。
	InputRaw []ConfigRaw `json:"input,omitempty" yaml:"input"`

	// JSON 和 YAML 解析时的过滤器配置对象数组。
	FilterRaw []ConfigRaw `json:"filter,omitempty" yaml:"filter"`

	// JSON 和 YAML 解析时的输出配置对象数组。
	OutputRaw []ConfigRaw `json:"output,omitempty" yaml:"output"`

	// 配置事件相关参数，如日志事件处理逻辑。使用logevent.Config类型封装。
	Event *logevent.Config `json:"event,omitempty" yaml:"event"`

	// 通道大小设置：in_filter, filter_out 和 out_debug
	// 分别代表输入到过滤器、过滤器到输出和输出到调试的通道大小（以字节为单位）。
	ChannelSize int `json:"chsize,omitempty" yaml:"chsize"`

	// 工作者数量，如果未设置则默认值为1。
	Worker int `json:"worker,omitempty" yaml:"worker"`

	// 是否启用调试通道，用于测试环境。布尔值类型。
	DebugChannel bool `json:"debugch,omitempty" yaml:"debugch"`

	// chInFilter：输入到过滤器的通道（MsgChan类型）。
	chInFilter MsgChan

	// chFilterOut：过滤器到输出的通道（MsgChan类型）。
	chFilterOut MsgChan

	// chOutDebug：输出到调试的通道（MsgChan类型）。
	chOutDebug MsgChan

	// 上下文环境，用于处理并发和取消操作等。
	ctx context.Context

	// errgroup.Group实例化对象，用于管理多个协程并确保它们按顺序完成或失败。
	eg *errgroup.Group

	// 状态变量：整数类型表示当前运行状态（如未运行、运行中或已停止）。
	state int32

	// 广播器，用于触发暂停事件的分布式通知机制。
	signalPause *ctxutil.Broadcaster

	// 广播器，用于触发恢复事件的分布式通知机制。
	signalResume *ctxutil.Broadcaster
}

// Set default config
var defaultConfig = Config{
	ChannelSize: 100,
	Worker:      1,
}

// MsgChan message channel type
type MsgChan chan logevent.LogEvent

// LoadFromFile 函数用于从指定的文件路径加载配置数据。
func LoadFromFile(path string) (config Config, err error) {
	// 首先尝试读取文件内容。如果发生错误，返回错误信息和默认的配置结构体。
	data, err := os.ReadFile(path)
	if err != nil {
		return config, ErrorReadConfigFile1.New(err, path) // 使用自定义错误工厂生成错误代码和提供路径参数
	}

	// 根据文件扩展名来判断文件格式，可能是 YAML 或 JSON 类型。
	ext := strings.ToLower(filepath.Ext(path))

	switch ext {
	case ".yml", ".yaml": // 如果扩展名为 .yml 或者 .yaml，则尝试从数据加载 YAML 配置
		config, err = LoadFromYAML(data)
	default: // 否则，假设文件格式为 JSON 类型，并尝试从数据加载 JSON 配置。
		config, err = LoadFromJSON(data)
	}

	return config, err // 返回加载后的配置和可能产生的错误
}

// LoadFromJSON 函数用于从 JSON 格式的数据中加载配置。
func LoadFromJSON(data []byte) (config Config, err error) {
	// 首先尝试清理输入数据中的注释。如果此步骤失败或读取数据时发生错误，则直接返回错误信息，不进行后续操作。
	if data, err = cleanComments(data); err != nil {
		return
	}

	// 尝试将清理后的 JSON 数据解码为 Config 结构体。如果解码过程中发生错误，则使用自定义错误工厂生成错误代码和包含原始错误的信息来返回错误。
	if err = json.Unmarshal(data, &config); err != nil {
		return config, ErrorUnmarshalJSONConfig.New(err)
	}

	// 对加载的配置进行初始化，可能包括设置默认值或执行一些特定于配置的初始化逻辑（这在实际代码中可能需要具体的实现）。
	initConfig(&config)

	// 返回加载后的完整配置和任何可能产生的错误。如果所有步骤都顺利完成，则返回有效配置与 nil 错误。
	return
}

// LoadFromYAML 函数用于从 YAML 格式的数据中加载配置。
func LoadFromYAML(data []byte) (config Config, err error) {
	// 尝试使用 yaml.Unmarshal 解码输入的 YAML 数据到指定的 config 结构体上。如果解码过程中出现错误，
	// 则通过返回当前解析出的配置对象和一个自定义错误代码，该代码包含原错误信息。
	if err = yaml.Unmarshal(data, &config); err != nil {
		return config, ErrorUnmarshalYAMLConfig.New(err)
	}

	// 对加载的配置进行初始化处理。这可能包括设置默认值或者执行一些特定于配置文件的初始化操作（具体实现依赖于实际业务需求）。
	initConfig(&config)

	// 成功完成解析和初始化后，返回完整的配置对象与错误状态（如果存在）。
	return
}

// 初始化配置参数
func initConfig(config *Config) {
	rv := reflect.ValueOf(&config)
	formatReflect(rv)

	if config.ChannelSize < 1 {
		config.ChannelSize = defaultConfig.ChannelSize // 若配置中通道大小小于1，则将其设置为默认值。
	}
	if config.Worker < 1 {
		config.Worker = defaultConfig.Worker // 类似地，若工作线程数量小于1，则设置为默认值。
	}
	if config.Event != nil { // 如果有事件配置
		logevent.SetConfig(config.Event) // 设置日志事件处理的配置。
	}

	config.chInFilter = make(MsgChan, config.ChannelSize)
	config.chFilterOut = make(MsgChan, config.ChannelSize)
	if config.DebugChannel {
		config.chOutDebug = make(MsgChan, config.ChannelSize) // 若设置了debug通道，则创建一个debug输出队列。
	}
	config.state = stateNormal
	config.signalPause = ctxutil.NewBroadcaster()
	config.signalResume = ctxutil.NewBroadcaster() // 初始化暂停和恢复信号的广播器。
}

// 在goroutines中启动配置
func (t *Config) Start(ctx context.Context) (err error) {
	ctx = contextWithOSSignal(ctx, goglog.Logger, os.Interrupt, syscall.SIGTERM) // 将系统中断信号绑定到上下文中。

	t.eg, t.ctx = errgroup.WithContext(ctx)

	if err = t.startInputs(); err != nil {
		return
	}
	if err = t.startFilters(); err != nil {
		return
	}
	if err = t.startOutputs(); err != nil {
		return
	}

	return // 所有操作成功则返回nil，若中间有任何错误则直接返回。
}

// Wait阻塞直到所有过滤器运行完毕，然后返回第一个非nil的错误（如果有的话）。
func (t *Config) Wait() (err error) {
	return t.eg.Wait()
}

// TestInputEvent向chInFilter发送一个事件，用于测试
func (t *Config) TestInputEvent(event logevent.LogEvent) {
	t.chInFilter <- event // 将事件传入输入队列。
}

// TestGetOutputEvent从chOutDebug获取一个事件，用于测试
func (t *Config) TestGetOutputEvent(timeout time.Duration) (event logevent.LogEvent, err error) {
	ctx, cancel := context.WithTimeout(t.ctx, timeout)
	defer cancel() // 取消上下文的超时。

	select {
	case <-ctx.Done(): // 如果在超时前上下文关闭
		return // 返回nil错误和一个事件值为nil的event。
	case ev := <-t.chOutDebug: // 否则，如果过滤器在指定时间内输出了事件
		return ev, nil
	case <-time.After(timeout + 10*time.Millisecond): // 如果过滤器未在超时时间前产出事件，并且超过了额外的延迟时间
		return event, ErrorTimeout1.New(nil, timeout) // 返回错误和timeout信息。
	}
}
